package com.lishui.waterfriend.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.lishui.waterfriend.common.result.PageResult;
import com.lishui.waterfriend.contant.MessageConstant;
import com.lishui.waterfriend.contant.RedisVarConstant;
import com.lishui.waterfriend.contant.UserConstant;
import com.lishui.waterfriend.context.BaseContext;
import com.lishui.waterfriend.enums.TeamStatusEnum;
import com.lishui.waterfriend.exception.BusinessException;
import com.lishui.waterfriend.exception.ParameterException;
import com.lishui.waterfriend.exception.TeamCreateFailedException;
import com.lishui.waterfriend.mapper.UserMapper;
import com.lishui.waterfriend.mapper.UserTeamMapper;
import com.lishui.waterfriend.pojo.dto.TeamJoinDTO;
import com.lishui.waterfriend.pojo.dto.TeamQueryDTO;
import com.lishui.waterfriend.pojo.dto.TeamUpdateDTO;
import com.lishui.waterfriend.pojo.entity.Team;
import com.lishui.waterfriend.pojo.entity.User;
import com.lishui.waterfriend.pojo.entity.UserTeam;
import com.lishui.waterfriend.pojo.vo.TeamUserVO;
import com.lishui.waterfriend.pojo.vo.UserVO;
import com.lishui.waterfriend.service.TeamService;
import com.lishui.waterfriend.mapper.TeamMapper;
import com.lishui.waterfriend.service.UserService;
import com.lishui.waterfriend.service.UserTeamService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
* @author 24197
* @description 针对表【team(队伍)】的数据库操作Service实现
* @createDate 2024-04-03 01:32:44
*/
@Service
@Slf4j
public class TeamServiceImpl extends ServiceImpl<TeamMapper, Team>
    implements TeamService{
    @Resource
    private UserTeamService userTeamService;

    @Resource
    private UserService userService;

    @Resource
    private UserMapper userMapper;

    @Resource
    private TeamMapper teamMapper;

    @Resource
    private UserTeamMapper userTeamMapper;

    @Resource
    private RedissonClient redissonClient;

    @Lazy
    @Resource
    private TeamService teamService;

    @Override
    @Transactional
    public Long saveTeam(Team team) {
        // 1. 判断参数是否为空
        if (team == null) throw new ParameterException(MessageConstant.EMPTY_PARAMETER);
        // 2. 是否登录，提前已经做过登录验证。
        // 3.校验信息
        // (1).队伍人数 > 1 且 <= 20
        Integer maxNum = Optional.ofNullable(team.getMaxNum()).orElse(0);
        if (maxNum <= 1 || maxNum > 20) {
            throw new ParameterException(MessageConstant.UNUSUAL_TEAM_SIZE);
        }
        // (2).队伍标题 <= 20
        String name = team.getName();
        if (StringUtils.isBlank(name) || name.length() > 20) {
            throw new ParameterException(MessageConstant.TEAM_TITLE_FORMAT_ERROR);
        }
        // (3).描述 <= 512
        String description = Optional.ofNullable(team.getDescription()).orElse("");
        if (description.length() > 512) {
            throw new ParameterException(MessageConstant.TEAM_DESCRIPTION_SIZE_MANY);
        }
        // (4).status是否公开，不传默认公开(0)。如果为加密一定要有密码，且密码<=32，不是加密不能加密码
        Integer status = Optional.ofNullable(team.getStatus()).orElse(0);
        TeamStatusEnum statusByValue = TeamStatusEnum.getStatusByValue(status);
        if (statusByValue == null) {
            throw new ParameterException(MessageConstant.ABNORMAL_TEAM_STATUS);
        }
        String password = team.getPassword();
        if ((TeamStatusEnum.SECRET.equals(statusByValue)) && (StringUtils.isBlank(password) || password.length() > 32)) {
            throw new ParameterException(MessageConstant.TEAM_PASSWORD_EXCEPTION + ": 加密密码不能为空");
        }
        if ((!TeamStatusEnum.SECRET.equals(statusByValue)) && StringUtils.isNotBlank(password)) {
            throw new ParameterException(MessageConstant.TEAM_PASSWORD_EXCEPTION + ": 不加密不能设密码");
        }
        // (5). 超时时间 > 当前实际
        Date expireTime = team.getExpireTime();
        if (expireTime == null || new Date().after(expireTime)) {
            throw new ParameterException(MessageConstant.ABNORMAL_TEAM_START_TIME);
        }
        // (6). 校验用户最多创建或加入5个队伍
        Long currentUserId = BaseContext.getCurrentId();
        // 加锁，相同用户同时只能创建一个队伍
        RLock userLock = redissonClient.getLock(String.format(RedisVarConstant.USER_LOCK, currentUserId));
        try {
            if (userLock.tryLock(0, -1, TimeUnit.MILLISECONDS)) {
                QueryWrapper<UserTeam> teamCountWrapper = getTeamWrapper(currentUserId);
                long count = userTeamMapper.selectCount(teamCountWrapper);
                if (count >= UserConstant.USER_MAX_JOIN_NUM) {
                    throw new TeamCreateFailedException(MessageConstant.USER_CREATION_EXCEEDS_LIMIT);
                }
                // 3. 插入队伍信息
                team.setUserId(BaseContext.getCurrentId());
                // 初始化当前人数为1
                team.setCurrentNum(1);
                boolean saveTeamResult = teamService.save(team);
                if (!saveTeamResult) throw new BusinessException(MessageConstant.SAVE_EXCEPTION + ": Team");
                // 4. 插入用户 => 队伍表里的关系
                UserTeam userTeam = new UserTeam();
                userTeam.setUserId(currentUserId);
                userTeam.setTeamId(team.getId());
                userTeam.setJoinTime(new Date());
                boolean saveTeamUserResult = userTeamService.save(userTeam);
                if (!saveTeamUserResult) throw new BusinessException(MessageConstant.SAVE_EXCEPTION + ": TeamUser");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            // 最后释放自己的锁
            if (userLock.isHeldByCurrentThread()) {
                log.info("unLock: " + Thread.currentThread().getId());
                userLock.unlock();
            }
        }
        return team.getId();
    }

    @Override
    public PageResult pageQuery(TeamQueryDTO teamQueryDTO) {
        QueryWrapper<Team> teamQueryWrapper = new QueryWrapper<>();
        String name = teamQueryDTO.getName();
        // 1. 若存在则作为查询条件
        if (StringUtils.isNotBlank(name)) {
            teamQueryWrapper.like("name", name);
        }
        String description = teamQueryDTO.getDescription();
        if (StringUtils.isNotBlank(description)) {
            teamQueryWrapper.like("description", description);
        }
        // 通过某个关键词对名称和描述同时查询
        String searchText = teamQueryDTO.getSearchText();
        if (StringUtils.isNotBlank(searchText)) {
            teamQueryWrapper.and(wrapper -> wrapper.like("name", searchText).or().like("description", searchText));
        }
        Integer maxNum = teamQueryDTO.getMaxNum();
        if (maxNum != null) {
            teamQueryWrapper.eq("max_num", maxNum);
        }
        Long userId = teamQueryDTO.getUserId();
        if (userId != null) {
            teamQueryWrapper.eq("user_id", userId);
        }
        // 2. 不展示已过期队伍
        teamQueryWrapper.gt("expire_time", new Date());
        // 3. 只有管理员才能查询非公开的房间
        Integer status = teamQueryDTO.getStatus();
        Long currentUserId = BaseContext.getCurrentId();
        boolean isAdmin = userService.isAdmin();
        Integer flag = teamQueryDTO.getFlag();
        // 当status不为空时，查询指定状态，公开和加密不限制，私有只有当前用户是队长或管理员才能查看。携带flag查自己相关的队伍不用限定status
        if (flag == null) {
            if (status != null) {
                TeamStatusEnum teamStatus = TeamStatusEnum.getStatusByValue(status);
                if (teamStatus != null) {
                    if (TeamStatusEnum.PRIVATE.equals(teamStatus)) {
                        // 管理员不传userId查全部私有 ， 队长传userId查自己私有
                        if (isAdmin || currentUserId.equals(userId)) {
                            teamQueryWrapper.eq("status", status);
                        } else {
                            throw new BusinessException(MessageConstant.MISSING_ADMINISTRATOR_RIGHTS);
                        }
                    } else {
                        teamQueryWrapper.eq("status", status);
                    }
                } else {
                    teamQueryWrapper.in("status", 0, 2);
                }
                // 当status为空时，默认查询公开和加密状态
            } else {
                teamQueryWrapper.in("status", 0, 2);
            }
        } else {
            // 当前用户加入的队伍
            if (flag == 0) {
                QueryWrapper<UserTeam> currentUserTeamWrapper = getTeamWrapper(currentUserId);
                List<UserTeam> currentUserTeamList = userTeamService.list(currentUserTeamWrapper);
                if (currentUserTeamList == null || currentUserTeamList.size() == 0) return null;
                List<Long> currentUserTeamIdList = currentUserTeamList.stream()
                                .map(UserTeam::getTeamId)
                                .collect(Collectors.toList());
                teamQueryWrapper.in("id", currentUserTeamIdList);
            }
            // 当前用户领队的队伍
            if (flag == 1) {
                teamQueryWrapper.eq("user_id", currentUserId);
            }
        }
        // 查询
        PageHelper.startPage(teamQueryDTO.getPageNum(), teamQueryDTO.getPageSize());
        List<Team> teamList = this.list(teamQueryWrapper);
        List<TeamUserVO> teamUserVOList = new ArrayList<>();
        PageInfo<Team> teamPageInfo = new PageInfo<>(teamList);
        teamList.forEach(team -> {
            TeamUserVO teamUserVO = new TeamUserVO();
            BeanUtils.copyProperties(team, teamUserVO);
            // 4. 队长信息
            UserVO leaderUserVO = new UserVO();
            User leaderUser = userService.getById(team.getUserId());
            BeanUtils.copyProperties(leaderUser, leaderUserVO);
            teamUserVO.setLeaderUser(leaderUserVO);
            // 5. 已加入用户列表
            List<UserVO> joinUserVOS = userMapper.getUserVOByTeamId(team.getId());
            teamUserVO.setUserVOList(joinUserVOS);
            teamUserVOList.add(teamUserVO);
        });
        return new PageResult(teamPageInfo.getTotal(), teamUserVOList);
    }

    @Override
    @Transactional
    public void updateTeam(TeamUpdateDTO teamUpdateDTO) {
        Long teamId = teamUpdateDTO.getId();
        Team oldTeam = checkTeamId(teamId);
        boolean isAdmin = userService.isAdmin();
        Long currentUserId = BaseContext.getCurrentId();
        if (!isAdmin && !currentUserId.equals(oldTeam.getUserId())) {
            throw new BusinessException(MessageConstant.MISSING_ADMINISTRATOR_RIGHTS);
        }
        String password = teamUpdateDTO.getPassword();
        Integer status = teamUpdateDTO.getStatus();
        Integer oldStatus = oldTeam.getStatus();
        // 当状态不变，密码改变。状态不为加密时，不能修改密码
        if (StringUtils.isNotBlank(password) && (status == null || status.equals(oldStatus))) {
            TeamStatusEnum oldTeamStatus = TeamStatusEnum.getStatusByValue(oldStatus);
            if (!TeamStatusEnum.SECRET.equals(oldTeamStatus)) {
                throw new BusinessException("当前状态不能修改密码");
            }
        }
        // 修改状态
        if (status != null && !status.equals(oldStatus)) {
            TeamStatusEnum teamStatus = TeamStatusEnum.getStatusByValue(status);
            if (TeamStatusEnum.SECRET.equals(teamStatus)) {
                // 修改为加密，并且密码为空。必须要设置密码
                if (StringUtils.isBlank(password)) {
                    throw new BusinessException("修改为加密状态需要密码");
                }
            } else {
                // 修改为隐私和公开要清除密码，并且不能传入密码
                if (StringUtils.isNotBlank(password)) {
                    throw new BusinessException("修改为隐私或公开不需要密码");
                }
                teamUpdateDTO.setPassword(null);
            }
          // 不改状态，不改密码，设置原来oldTeam的值防止被修改。
        } else if (StringUtils.isBlank(password)) {
            teamUpdateDTO.setPassword(oldTeam.getPassword());
        }
        teamMapper.updateTeamById(teamUpdateDTO);
    }

    @Override
    @Transactional
    public void join(TeamJoinDTO teamJoinDTO) {
        Long teamId = teamJoinDTO.getTeamId();
        Team team = checkTeamId(teamId);
        // 1. 只能加入未过期的队伍
        Date expireTime = team.getExpireTime();
        if (new Date().after(expireTime)) {
            throw new BusinessException(MessageConstant.ABNORMAL_TEAM_START_TIME);
        }
        // 2. 禁止私有队伍加入，加密需要匹配密码
        Integer status = team.getStatus();
        TeamStatusEnum teamStatus = TeamStatusEnum.getStatusByValue(status);
        String password = team.getPassword();
        String joinPassword = teamJoinDTO.getPassword();
        if (TeamStatusEnum.PRIVATE.equals(teamStatus)) {
            throw new BusinessException("私有队伍禁止加入");
        }
        if (TeamStatusEnum.SECRET.equals(teamStatus)) {
            if (!password.equals(joinPassword)) {
                throw new BusinessException(MessageConstant.PASSWORD_ERROR);
            }
        }
        // 分布式锁
        Long currentUserId = BaseContext.getCurrentId();
        RLock teamLock = redissonClient.getLock(String.format(RedisVarConstant.TEAM_LOCK, teamId));
        RLock userLock = redissonClient.getLock(String.format(RedisVarConstant.USER_LOCK, currentUserId));
        try {
            // 保证所有线程可以拿到锁，没拿到锁就一直重复抢，直到拿到或者超过抢锁次数5000为止
            int i = 0;
            while(true) {
                if (i >= 5000) break;
                // 只有一个线程能拿到锁
                if (userLock.tryLock(0, -1, TimeUnit.MILLISECONDS) && teamLock.tryLock(0, -1, TimeUnit.MILLISECONDS)) {
                    log.info("Lock:{} join加锁 {}", Thread.currentThread().getId(), new Date());
                    // 3. 查询用户加入的队伍数，用户最多加入5个队伍
                    QueryWrapper<UserTeam> teamCountWrapper = getTeamWrapper(currentUserId);
                    Long teamCount = userTeamMapper.selectCount(teamCountWrapper);
                    if (teamCount >= UserConstant.USER_MAX_JOIN_NUM) {
                        throw new BusinessException(MessageConstant.USER_CREATION_EXCEEDS_LIMIT);
                    }
                    // 4. 不能加入重复和自己队伍
                    QueryWrapper<UserTeam> selfWrapper = getSelfWrapper(currentUserId, teamId);
                    Long selfCount = userTeamMapper.selectCount(selfWrapper);
                    if (selfCount >= 1) {
                        throw new BusinessException("不能重复加入已加入队伍");
                    }
                    // 5. 查询队伍已加入用户数，不能加入已满队伍
                    Integer maxNum = team.getMaxNum();
                    QueryWrapper<UserTeam> userCountWrapper = getUserWrapper(teamId);
                    Long userCount = userTeamMapper.selectCount(userCountWrapper);
                    if (userCount >= maxNum) {
                        throw new BusinessException(MessageConstant.UNUSUAL_TEAM_SIZE + ": 人数已满");
                    }
                    // 6. 新增用户-关联表
                    UserTeam userTeam = new UserTeam();
                    userTeam.setJoinTime(new Date());
                    userTeam.setTeamId(teamId);
                    userTeam.setUserId(currentUserId);
                    boolean saveTeamUserResult = userTeamService.save(userTeam);
                    // 统计人数
                    countCurrentNum(teamId);
                    if (!saveTeamUserResult) throw new BusinessException(MessageConstant.SAVE_EXCEPTION + ": TeamUser");
                    Thread.sleep(5000);
                    break;
                } else {
                    i ++ ;
                }
            }
        } catch (Exception e) {
            throw new BusinessException(e.getMessage());
        } finally {
            // 最后释放自己的锁
            if (userLock.isHeldByCurrentThread()) {
                log.info("unUserLock: " + Thread.currentThread().getId());
                userLock.unlock();
            }
            if (teamLock.isHeldByCurrentThread()) {
                log.info("unTeamLock: " + Thread.currentThread().getId());
                teamLock.unlock();
            }
        }
    }

    @Override
    @Transactional
    public void quit(Long teamId) {
        Team team = checkTeamId(teamId);
        Long currentUserId = BaseContext.getCurrentId();
        // 1. 判断用户是否已加入该队伍
        QueryWrapper<UserTeam> selfWrapper = getSelfWrapper(currentUserId, teamId);
        Long selfCount = userTeamMapper.selectCount(selfWrapper);
        if (selfCount <= 0) {
            throw new BusinessException("当前用户没有加入该队伍");
        }
        QueryWrapper<UserTeam> userCountWrapper = getUserWrapper(teamId);
        Long userCount = userTeamMapper.selectCount(userCountWrapper);
        if (userCount <= 1) {
            // 2. 如果队伍只剩一人，解散
            QueryWrapper<Team> removeTeamQueryWrapper = new QueryWrapper<>();
            Team t = new Team();
            t.setId(teamId);
            t.setCurrentNum(0);
            updateById(t);
            removeTeamQueryWrapper.eq("id", teamId);
            this.remove(removeTeamQueryWrapper);
        } else {
            // 3. 如果不止一人，且退出是队长，需要顺位到第二加入的用户
            if (currentUserId.equals(team.getUserId())) {
                QueryWrapper<UserTeam> first2UserQueryWrapper = new QueryWrapper<>();
                first2UserQueryWrapper.eq("team_id", teamId);
                first2UserQueryWrapper.last("order by id asc limit 2");
                List<UserTeam> list = userTeamService.list(first2UserQueryWrapper);
                UserTeam next = list.get(1);
                Team updateTeam = new Team();
                updateTeam.setId(teamId);
                updateTeam.setUserId(next.getUserId());
                teamMapper.updateById(updateTeam);
            }
        }
        QueryWrapper<UserTeam> removeUserTeamQueryWrapper = new QueryWrapper<>();
        removeUserTeamQueryWrapper.eq("user_id", currentUserId);
        removeUserTeamQueryWrapper.eq("team_id", teamId);
        userTeamService.remove(removeUserTeamQueryWrapper);
        // 统计人数
        countCurrentNum(teamId);
    }

    @Override
    @Transactional
    public void disband(Long teamId) {
        Team team = checkTeamId(teamId);
        Long currentUserId = BaseContext.getCurrentId();
        if (!currentUserId.equals(team.getUserId()) && !userService.isAdmin()) {
            throw new BusinessException(MessageConstant.MISSING_ADMINISTRATOR_RIGHTS);
        }
        // 1. 移除所有加入队伍的关联信息
        QueryWrapper<UserTeam> userTeamRemoveWrapper = getUserWrapper(teamId);
        userTeamService.remove(userTeamRemoveWrapper);
        // 2.移除队伍
        QueryWrapper<Team> teamRemoveWrapper = new QueryWrapper<>();
        teamRemoveWrapper.eq("id", teamId);
        // 统计人数后，再删除
        countCurrentNum(teamId);
        this.remove(teamRemoveWrapper);
    }

    /**
     * 判断id是否为空 查询队伍是否存在
     * @param teamId
     * @return
     */
    public Team checkTeamId(Long teamId) {
        if (teamId == null) throw new ParameterException("id: " + MessageConstant.EMPTY_PARAMETER);
        Team team = this.getById(teamId);
        if (team == null) throw new ParameterException("team: " + MessageConstant.NO_EXISTS);
        return team;
    }

    /**
     * 获取user_team表，查询用户加入的队伍 的查询条件
     * @param userId
     * @return
     */
    public QueryWrapper<UserTeam> getTeamWrapper(Long userId) {
        QueryWrapper<UserTeam> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_id", userId);
        return queryWrapper;
    }

    /**
     * 获取user_team表，查询当前队伍拥有用户 的查询条件
     * @param teamId
     * @return
     */
    public QueryWrapper<UserTeam> getUserWrapper(Long teamId) {
        QueryWrapper<UserTeam> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("team_id", teamId);
        return queryWrapper;
    }

    /**
     * 查询用户是否已加入当前队伍条件
     * @param userId
     * @param teamId
     * @return
     */
    public QueryWrapper<UserTeam> getSelfWrapper(Long userId, Long teamId) {
        QueryWrapper<UserTeam> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("user_id", userId);
        queryWrapper.eq("team_id", teamId);
        return queryWrapper;
    }

    /**
     * 加入，退出，解散 队伍后统计队伍当前人数
     * @param teamId
     */
    public void countCurrentNum(Long teamId) {
        QueryWrapper<UserTeam> userCountWrapper = getUserWrapper(teamId);
        Long currentNum = userTeamService.count(userCountWrapper);
        Team team = new Team();
        team.setId(teamId);
        team.setCurrentNum(currentNum.intValue());
        this.updateById(team);
    }
}




